package edu.kufpg.armatus.console;
import edu.kufpg.armatus.util.StringUtils;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.style.LeadingMarginSpan;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
/**
* An {@link EditText} that has two unique properties:
* <ol>
* <li>The first line of a {@code ConsoleInputEditText} is indented. This is intended
* to allow for a custom prompt (e.g., {@code armatus@android~$}) to be placed in the
* space left by the indentation.</li>
* <li>This class does not use the default word-wrapping behavior of {@link
* android.widget.TextView TextView}; instead, {@code ConsoleInputEditText} wraps by
* character to more closely resemble an actual terminal.</li>
* </ol>
*/
public class ConsoleInputEditText extends EditText {
/**
* The Q key isn't important in itself; I just need to type some character in for
* a workaround to fix {@link LeadingMarginSpan} issues.
*/
private static final KeyEvent Q = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_Q);
/**
* The delete key, for immediately deleting the "Q" that I type in a workaround to fix
* some annoying {@link LeadingMarginSpan} issues.
*/
private static final KeyEvent DEL = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
/** The span which defines this {@link android.widget.TextView TextView}'s indent amount. */
private LeadingMarginSpan.Standard mIndent;
public ConsoleInputEditText(Context context) {
super(context);
init();
}
public ConsoleInputEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ConsoleInputEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mIndent = new LeadingMarginSpan.Standard(0, 0);
getText().setSpan(mIndent, 0, 0, 0);
addTextChangedListener(new TextWatcher() {
/** Tracks the beginning index (inclusive) of the most recent text change. */
private int mChangedStartIndex;
/** Tracks the end index (exclusive) of the most recent text change. */
private int mChangedEndIndex;
@Override
public void afterTextChanged(Editable s) {
int cursorPos = getSelectionStart();
removeTextChangedListener(this);
//Remove additional spans that might be introduced through pasting
for (LeadingMarginSpan span : s.getSpans(0, s.length(), LeadingMarginSpan.class)) {
s.removeSpan(span);
}
s.setSpan(mIndent, 0, 0, 0);
for (; mChangedStartIndex < mChangedEndIndex; mChangedStartIndex++) {
switch (s.charAt(mChangedStartIndex)) {
case ' ':
//Prevent TextView's default word-wrapping behavior (wrap by character instead)
s.replace(mChangedStartIndex, mChangedStartIndex+1, StringUtils.NBSP);
break;
case '\n':
/* A strange bug exists where pasting multiple lines will seemingly indent all
* newlines--until the text is changed via typing. Therefore, this will initiate
* the typing on each newline to prevent weirdness. */
setSelection(mChangedStartIndex);
dispatchKeyEvent(Q);
dispatchKeyEvent(DEL);
break;
}
}
addTextChangedListener(this);
setSelection(cursorPos);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
mChangedStartIndex = start;
mChangedEndIndex = start + after;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
/**
* Sets the length of the indentation. Note that if you are using a custom prompt,
* the length of the indentation should be the length of the prompt minus the prompt's
* padding.
* @param length of the indentation.
*/
public void setIndent(int length) {
//First remove the original indent(s)
for (LeadingMarginSpan span : getText().getSpans(0, getText().length(), LeadingMarginSpan.class)) {
getText().removeSpan(span);
}
mIndent = new LeadingMarginSpan.Standard(length, 0);
getText().setSpan(mIndent, 0, 0, 0);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
//Hack to allow for entry submission when the Enter key is pushed.
InputConnection conn = super.onCreateInputConnection(outAttrs);
outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
return conn;
}
}